package bean.juego;

import java.io.BufferedReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import logic.Buscador;
import bean.Arma;
import bean.Equipo;
import bean.Logro;
import bean.Muerte;
import bean.jugador.Jugador;
import util.Dummies;
import util.JugadoresComparator;
import util.Pair;
import logic.cache.CacheCaller;
import logic.cache.CacheService;

public abstract class Juego {

    public static final SimpleDateFormat fechaJuegoFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static final Pattern patternInitGame = Pattern.compile("(.*?) InitGame: .*");
    private static final Logger logger = Logger.getLogger(Juego.class.getName());
    private static final Pattern patternAward = Pattern.compile(".*?Award: .*?: (.*?) gained the (.*?) award!");
    private static final Pattern patternKill = Pattern.compile(".*?Kill: .*?: (.*?) killed (.*?) by (.*?)");
    private static final Pattern patternPlayerScore = Pattern.compile("(.*?) PlayerScore: .*?: (.*?) now has (.*?) points");
    private static final Pattern patternShutdownGame = Pattern.compile("(.*?) ShutdownGame:.*");
    protected Map<Jugador, Integer> mapaPuntosJugador;
    protected Map<Integer, Map<Jugador, Integer>> mapaPuntosJugadorTiempo;
    protected List<Muerte> muertes;
    protected List<Pair<Logro, Jugador>> logros;
    protected boolean valido;
    protected boolean terminado;
    protected int tiempoInicio;
    protected int tiempoFin;
    protected Map<Equipo, Set<Jugador>> equipos;
    protected Equipo ganador;
    protected final Map<String, String> parametrosJuego;
    protected Date fechaJuego;

    protected Juego(){
        throw new UnsupportedOperationException();
    }

    public Juego(Map<String, String> parametrosJuego) {
        super();
        this.parametrosJuego = Collections.unmodifiableMap(parametrosJuego);
        this.mapaPuntosJugador = new HashMap<Jugador, Integer>();
        this.mapaPuntosJugadorTiempo = new HashMap<Integer, Map<Jugador, Integer>>();
        this.muertes = new ArrayList<Muerte>();
        this.logros = new ArrayList<Pair<Logro, Jugador>>();
        this.valido = false;
        this.terminado = false;
        this.equipos = new EnumMap<Equipo, Set<Jugador>>(Equipo.class);

        for (Equipo equipo : Equipo.values()) {
            this.equipos.put(equipo, new HashSet<Jugador>());
        }

        String g_timestamp = parametrosJuego.get(ParametrosJuego.g_timestamp.toString());
        if (g_timestamp != null) {
            try {
                this.fechaJuego = fechaJuegoFormat.parse(g_timestamp);
            } catch (ParseException ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }
    }

    public void nuevaMuerte(String nvictimario, String nvictima, String narma) {
        if ("<world>".equals(nvictimario)) {
            return;
        }

        Jugador victima = Buscador.jugadorPorApodo(nvictima);
        Jugador victimario = Buscador.jugadorPorApodo(nvictimario);
        Arma arma = Buscador.armaPorId(narma);

        this.muertes.add(new Muerte(victima, victimario, arma));
    }

    /*
     * private void calcularEquipoPorMuerte(Muerte muerte) { Equipo equipo =
     * this.getEquipo(muerte.getVictima()); if (equipo != null) {
     * this.equipos.get(this.contrario(equipo)).add(muerte.getVictimario()); }
     * else { equipo = this.getEquipo(muerte.getVictimario()); if (equipo !=
     * null) {
     * this.equipos.get(this.contrario(equipo)).add(muerte.getVictima()); } } }
     */
    public void nuevoLogro(String njugador, String nlogro) {
        Jugador victimario = Buscador.jugadorPorApodo(njugador);
        Logro logro = Logro.valueOf(nlogro);

        this.logros.add(new Pair<Logro, Jugador>(logro, victimario));
    }

    public void nuevosPuntos(String tiempo, String njugador, String puntos) {
        Jugador jugador = Buscador.jugadorPorApodo(njugador);
        this.mapaPuntosJugador.put(jugador, Integer.valueOf(puntos));

        Integer segundos = toSeconds(tiempo) - this.tiempoInicio;
        if (!this.mapaPuntosJugadorTiempo.containsKey(segundos)) {
            this.mapaPuntosJugadorTiempo.put(segundos, new HashMap<Jugador, Integer>());
        }

        Map<Jugador, Integer> map = this.mapaPuntosJugadorTiempo.get(segundos);
        for (Map.Entry<Jugador, Integer> entry : this.mapaPuntosJugador.entrySet()) {
            map.put(entry.getKey(), entry.getValue());
        }
    }

    public List<Pair<Logro, Jugador>> getLogros() {
        return this.logros;
    }

    public Map<Jugador, Integer> getMapaPuntosJugador() {
        return this.mapaPuntosJugador;
    }

    public List<Muerte> getMuertes() {
        return this.muertes;
    }

    public boolean isValido() {
        return this.valido;
    }

    public boolean isTerminado() {
        return this.terminado;
    }

    public void finalizar(String tiempoFin) {
        this.tiempoFin = toSeconds(tiempoFin);
    }

    public void setInicio(String tiempoInicio) {
        this.tiempoInicio = toSeconds(tiempoInicio);
    }

    public Map<String, String> getParametrosJuego() {
        return this.parametrosJuego;
    }

    public Date getFechaJuego() {
        return this.fechaJuego;
    }

    protected static int toSeconds(String tiempo) {
        int tiempoSegundos = 0;

        tiempo = tiempo.trim();

        String[] split = tiempo.split(":");

        try {
            for (String string : split) {
                tiempoSegundos = tiempoSegundos * 60 + Integer.valueOf(string);
            }
        } catch (NumberFormatException e) {
            logger.log(Level.SEVERE, null, e);
        }
        return tiempoSegundos;
    }

    public int getTiempoEnSegundos() {
        return this.tiempoFin - this.tiempoInicio;
    }

    protected Equipo getEquipo(Jugador jugador) {
        for (Equipo equipo : Equipo.values()) {
            if (this.equipos.get(equipo).contains(jugador)) {
                return equipo;
            }
        }

        return null;
    }

    public void setGanadores(String line) {
        String[] split = line.split(" ");

        int countRed = 0;
        int countBlue = 0;
        for (String string : split) {
            if (string.contains(Equipo.RED.toString().toLowerCase())) {
                String[] split2 = string.split(":");
                countRed = Integer.parseInt(split2[1]);
            } else if (string.contains(Equipo.BLUE.toString().toLowerCase())) {
                String[] split2 = string.split(":");
                countBlue = Integer.parseInt(split2[1]);
            }
        }

        if (countBlue > countRed) {
            this.ganador = Equipo.BLUE;
        } else {
            this.ganador = Equipo.RED;
        }
    }

    public boolean esGanador(Jugador jugador) {
        return this.esJugador(jugador) ? this.equipos.get(this.ganador).contains(jugador) : false;
    }

    public int getMuertesRecibidas(Jugador jugador) {
        int cant = 0;

        for (Muerte muerte : this.muertes) {
            if (muerte.getVictima().equals(jugador)) {
                cant++;
            }
        }

        return cant;
    }

    public int getMuertesHechas(final Jugador jugador) {
        CacheCaller<Integer> caller = new CacheCaller<Integer>() {

            @Override
            public String getKey() {
                return Juego.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(Juego.this) + ".getMuertesHechas:" + jugador;
            }

            @Override
            public Integer callMethod() {
                int cant = 0;

                for (Muerte muerte : Juego.this.muertes) {
                    if (muerte.getVictimario().equals(jugador)) {
                        cant++;
                    }
                }

                return cant;
            }
        };

        return CacheService.getFromCache(caller);
    }

    public boolean esJugador(Jugador jugador) {
        return this.mapaPuntosJugador.containsKey(jugador);
    }

    public List<Jugador> getEquipo(Equipo equipo) {
        List<Jugador> jugadores = new ArrayList<Jugador>();

        jugadores.addAll(this.equipos.get(equipo));

        return jugadores;
    }

    public List<Jugador> getMVPHumano() {

        CacheCaller<List<Jugador>> caller = new CacheCaller<List<Jugador>>() {

            @Override
            public String getKey() {
                return Juego.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(Juego.this) + ".getMVPHumano";
            }

            @Override
            public List<Jugador> callMethod() {
                Jugador bot = Buscador.jugadorPorApodo("Bot");
                int maxPuntos = 0;

                List<Jugador> jugadores = new ArrayList<Jugador>();

                for (Map.Entry<Jugador, Integer> entry : Juego.this.mapaPuntosJugador.entrySet()) {
                    if (entry.getKey().equals(bot)) {
                        continue;
                    }
                    if (maxPuntos < entry.getValue()) {
                        maxPuntos = entry.getValue();
                        jugadores.clear();
                        jugadores.add(entry.getKey());
                    } else if (maxPuntos == entry.getValue()) {
                        jugadores.add(entry.getKey());
                    }
                }

                return Collections.unmodifiableList(jugadores);
            }
        };

        return CacheService.getFromCache(caller);
    }

    public List<Jugador> getJugadores() {
        List<Jugador> jugadores = new ArrayList<Jugador>();

        jugadores.addAll(this.mapaPuntosJugador.keySet());

        Collections.sort(jugadores, JugadoresComparator.getInstancia());

        return jugadores;
    }

    public int getMuertesJugadorAJugador(final Jugador victimario, final Jugador victima) {

        CacheCaller<Integer> caller = new CacheCaller<Integer>() {

            @Override
            public String getKey() {
                return Juego.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(Juego.this) + ".getMuertesJugadorAJugador:" + victimario + ":" + victima;
            }

            @Override
            public Integer callMethod() {
                int cant = 0;

                for (Muerte muerte : Juego.this.muertes) {
                    if (muerte.getVictima().equals(victima) && muerte.getVictimario().equals(victimario)) {
                        cant++;
                    }
                }

                return cant;
            }
        };

        return CacheService.getFromCache(caller);
    }

    protected void accionesFinDeJuego() {
        if (this.isValido()) {
            this.cargarMuertes();
            this.cargarLogros();
            this.cargarPuntos();
        }
    }

    private void cargarMuertes() {
        for (Muerte muerte : this.getMuertes()) {
            Jugador victima = muerte.getVictima();

            if (!muerte.esSuicidio()) {
                Jugador victimario = muerte.getVictimario();
                Arma arma = muerte.getArma();

                victima.getMuertesRecibidas().add(muerte);
                victimario.getMuertesHechas().add(muerte);
                arma.sumar();
            }
        }
    }

    private void cargarLogros() {
        for (Pair<Logro, Jugador> logro : this.getLogros()) {
            Jugador victimario = logro.getSecond();

            victimario.sumarLogro(logro.getFirst());
        }
    }

    private void cargarPuntos() {
        Map<Jugador, Integer> map = this.getMapaPuntosJugador();

        for (Jugador jugador : map.keySet()) {
            jugador.agregarJuego(this);
        }
    }

    public abstract boolean procesarLinea(BufferedReader leer) throws IOException;

    protected boolean procesarLinea(String linea) {
        //this.md5Hash = MD5Calculator.stringToMD5(this.md5Hash + linea);
        Matcher m;

        m = patternAward.matcher(linea);
        if (m.matches()) {
            this.nuevoLogro(m.group(1), m.group(2));
            return true;
        }

        m = patternKill.matcher(linea);
        if (m.matches()) {
            this.nuevaMuerte(m.group(1), m.group(2), m.group(3));
            return true;
        }

        m = patternPlayerScore.matcher(linea);
        if (m.matches()) {
            this.nuevosPuntos(m.group(1), m.group(2), m.group(3));
            return true;
        }

        m = patternShutdownGame.matcher(linea);
        if (m.matches()) {
            this.terminado = true;
            return true;
        }

        return false;
    }

    public boolean sonMismoEquipo(Collection<Jugador> jugadores) {
        boolean son = false;

        for (Equipo equipo : Equipo.values()) {
            son = this.equipos.get(equipo).containsAll(jugadores);

            if (son) {
                break;
            }
        }

        return son;
    }

    private Map<Jugador, Integer> getPuntosJugadorTiempo(int tiempo) {

        if (this.mapaPuntosJugadorTiempo.containsKey(tiempo)) {
            Map<Jugador, Integer> retorno = this.mapaPuntosJugadorTiempo.get(tiempo);

            for (Map.Entry<Jugador, Integer> entry : this.mapaPuntosJugador.entrySet()) {
                Jugador jugador = entry.getKey();
                if (!retorno.containsKey(jugador)) {
                    retorno.put(jugador, 0);
                }
            }

            return retorno;
        } else {
            if (tiempo <= 0) {
                Map<Jugador, Integer> retorno = new HashMap<Jugador, Integer>();

                for (Map.Entry<Jugador, Integer> entry : this.mapaPuntosJugador.entrySet()) {
                    retorno.put(entry.getKey(), 0);
                }

                return retorno;
            } else {
                return this.getPuntosJugadorTiempo(tiempo - 1);
            }
        }
    }

    public List<Map<Jugador, Integer>> getPuntosJugadorTiempo() {

        CacheCaller<List<Map<Jugador, Integer>>> caller = new CacheCaller<List<Map<Jugador, Integer>>>() {

            @Override
            public String getKey() {
                return Juego.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(Juego.this) + ".getPuntosJugadorTiempo";
            }

            @Override
            public List<Map<Jugador, Integer>> callMethod() {
                List<Map<Jugador, Integer>> retorno = new ArrayList<Map<Jugador, Integer>>();
                for (int i = 0; i < Juego.this.tiempoFin - Juego.this.tiempoInicio; i++) {
                    retorno.add(Collections.unmodifiableMap(Juego.this.getPuntosJugadorTiempo(i)));
                }

                return Collections.unmodifiableList(retorno);
            }
        };

        return CacheService.getFromCache(caller);
    }

    public int getMaximoPuntos() {

        CacheCaller<Integer> caller = new CacheCaller<Integer>() {

            @Override
            public String getKey() {
                return Juego.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(Juego.this) + ".getMaximoPuntos";
            }

            @Override
            public Integer callMethod() {
                int maximo = 0;

                for (Map.Entry<Jugador, Integer> entry : Juego.this.mapaPuntosJugador.entrySet()) {
                    Integer integer = entry.getValue();
                    if (integer > maximo) {
                        maximo = integer;
                    }
                }

                return maximo;
            }
        };

        return CacheService.getFromCache(caller);
    }

    public void cargarEnSistema() {
        if (this.isValido()) {
            this.accionesFinDeJuego();
            Buscador.addJuego(this);
        }
    }
}
